package pers.cfeng.common;

import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 引用计数方式的缓存策略
 * Long 为资源编号， T为实际的数据，关联的Integer为引用计数, 关联的boolean是是否正在获取
 */
public abstract class AbstractDataCache<T> {

    private HashMap<Long, T> cache; //当前能够缓存的实际的数据
    private HashMap<Long, Boolean> accessing; //多线程下正在被获取的数据
    private HashMap<Long, Integer> references; //每个资源引用计数个数

    private int maxResource; //缓存中最大的资源数
    private int count = 0; //当前缓存中的元素个数, 其实就是cache的长度
    private Lock lock; //多线程并发安全

    public AbstractDataCache(int maxResource) {
        this.maxResource = maxResource;
        cache = new HashMap<>();
        references = new HashMap<>();
        accessing = new HashMap<>();
        lock = new ReentrantLock(); //可重入锁
    }

    /**
     * cpu空转一直尝试从cache获取，如果有其余线程正在获取，就等待一会sleep
     */
    protected T get(long key) throws Exception {
        //cpu空转等待直到获取到key, 或者缓存中存在直接返回, 因为涉及hashMap数据操作，需要加锁操作
        while(true) {
            //尝试获取时先加锁读取
            lock.lock();
            //其余线程在处理，睡眠等待， 每一次操作都需要unlock
            if(accessing.containsKey(key)) {
                lock.unlock();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    continue;
                }
                continue;
            }

            //cache中存在，获取的时候计数 + 1
            if(cache.containsKey(key)) {
                T obj = cache.get(key);
                references.put(key, references.get(key) + 1);
                lock.unlock();
                return obj;
            }

            //cache满
            if(maxResource > 0 && count == maxResource) {
                lock.unlock();
                throw new DatabaseException(ErrorCode.CACHE_FULL);
            }

            //不在cache中,首先给当前设置为true,表明获取到
            count ++;
            accessing.put(key, true);
            lock.unlock();
            break;
        }
        //获取到之后需要调取放进cache
        T obj = null;
        try {
            obj = getToCache(key);
        } catch (Exception e) {
            lock.lock(); //获取失败, count变回去
            count --;
            accessing.remove(key);
            lock.unlock();
        }
        //获取成功，放入cache
        lock.lock();
        accessing.remove(key);
        cache.put(key,obj);
        references.put(key, 1);
        lock.unlock();
        return obj;
    }

    /**
     * 强行释放缓存
     */
    protected void release(long key) {
        lock.lock();;
        try {
            int ref = references.get(key) - 1; //释放时计数 - 1, 类属性修改都需要加锁
            if(ref == 0) {
                T obj = cache.get(key);
                releaseForCache(obj); //回写资源
                references.remove(key); //引用计数也需要删除
                cache.remove(key); //cache标记的资源也移除
                count --;
            } else {
                references.put(key, ref); //引用计数
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 关闭缓存， 写回所有资源到文件中
     */
    protected void close() {
        lock.lock();
        try {
            Set<Long> keys = cache.keySet();
            for(long key: keys) {
                T obj = cache.get(key);
                releaseForCache(obj); //释放回写资源到文件
                references.remove(key);
                cache.remove(key);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 当资源不在cache中时，从源中获取
     */
    protected abstract T getToCache(long key) throws Exception;

    /**
     * 资源从cache中回写到源中
     */
    protected abstract void releaseForCache(T obj);
}